--[[
CastYeller2 - Announce important spell events.

Copyright (C) 2009 Adirelle

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--]]

--[===[@debug@
if type(GetBuildInfo) ~= 'function' then
	CastYeller2 = {}
	string.trim = function(s) return s end
	print("Testing")
	testing = true
	function wipe(t)
		local mt = getmetatable(t)
		setmetatable(t, nil)
		for k in pairs(t) do t[k] = nil end
		t._ = 1
		t._ = nil
		return setmetatable(t, mt)
	end
	CastYeller2.new = function(...) return {...} end
	CastYeller2.del = function() end
end
--@end-debug@]===]

if not CastYeller2 then return end
local CastYeller2 = CastYeller2
local L = CastYeller2.L

--------------------------------------------------------------------------------
-- Template compiler
--------------------------------------------------------------------------------

-- Have some functions handy
local strmatch, strfind, strtrim, strupper = string.match, string.find, string.trim, string.upper
local tinsert, tremove, tconcat = table.insert, table.remove, table.concat
local new, del = CastYeller2.new, CastYeller2.del

-- An iterator that splits the template in tokens
local IterateTokens
do
	-- Token patterns and names, the order matters
	local tokens = {
		{ '^%%(.)', "TEXT" },
		{ '^(%[)', "OPEN" },
		{ '^(%])', "CLOSE" },
		{ '^%$(%a+)', "SIMPLEVAR" },
		{ '^%$%{(%a+)%}', "QUOTEDVAR" },
		{ '^([^%[%]%{%}%$%%]+)', "TEXT" },
	}

	-- Test each pattern in order
	local function TokenIterator(template, pos)
		if pos > template:len() then
			return pos+1, "END", nil, "end of template"
		end
		for i, token in ipairs(tokens) do
			local pattern, name = unpack(token)
			local start, stop, value = strfind(template, pattern, pos)
			if start then
				return stop+1, name, value, "'"..template:sub(start, stop).."'"
			end
		end
		return template:len()+1, "ERROR", template:sub(pos), "'"..template:sub(pos).."'"
	end

	function IterateTokens(template)
		return TokenIterator, template, 1
	end
end

-- State machine transition table
-- Lists each possible transitions for each state
-- Each possible transiton gives the action to execute and the next state
local transitions = {
	TOPLEVEL = {
		OPEN = { "PUSH", "PREFIX" },
		SIMPLEVAR = { "VAR", "TOPLEVEL" },
		TEXT = { "CONCAT", "TOPLEVEL" },
		END = { "DONE" },
	},
	PREFIX = {
		OPEN = { "PUSH", "PREFIX" },
		TEXT = { "CONCAT", "PREFIX" },
		SIMPLEVAR = { "VAR", "PREFIX" },
		QUOTEDVAR = { "SETVAR", "SUFFIX" },
	},
	SUFFIX = {
		OPEN = { "PUSH", "PREFIX" },
		TEXT = { "CONCAT", "SUFFIX" },
		SIMPLEVAR = { "VAR", "SUFFIX" },
		CLOSE = { "POP" },
	},
}

local function Compile(template)
	local stack = new()
	local state = "TOPLEVEL"
	local pieces = new()
	local var = false
	local pos = 1
	for nextPos, token, value, match in IterateTokens(template) do
		--[===[@debug@
		if testing then
			print(pos, token, value, match)
		end
		--@end-debug@]===]
		if token == 'ERROR' then
			return false, "Unrecognized string "..match.." at position "..pos
		end
		local transition = transitions[state][token]
		if not transition then
			return false, "Unexpected "..match.." at position "..pos
		end
		local action, nextState = unpack(transition)
		if action == 'PUSH' then
			tinsert(stack, new(state, pieces, var))
			var = false
			pieces = new()
		elseif action == 'VAR' then
			value = strupper(value)
			tinsert(pieces, '(i.'..value..' or"")')
		elseif action == 'SETVAR' then
			value = strupper(value)
			tinsert(pieces, 'i.'..value)
			var = value
		elseif action == 'CONCAT' then
			tinsert(pieces, '[=['..value..']=]')
		elseif action == 'POP' then
			local piece = '(i.'..var..' and('..tconcat(pieces, '..')..')or"")'
			del(pieces)
			local top = tremove(stack)
			nextState, pieces, var = unpack(top)
			del(top)
			tinsert(pieces, piece)
		elseif action == 'DONE' then
			break
		end
		state, pos = nextState, nextPos
	end
	local code = "return function(i) return " .. tconcat(pieces,"..") .. " end"
	local func = loadstring(code, template)()
	del(stack)
	del(str)
	return true, func
end

--------------------------------------------------------------------------------
-- The formatting method
--------------------------------------------------------------------------------

local compiledTemplateCache = setmetatable({}, {__index = function(self, template)
	local ok, func = Compile(template)
	if not ok then
		print("CastYeller2: invalid template:", func)
		func = false
	end
	self[template] = func
	return func
end})

local variables = {}
function CastYeller2:FormatMessage(template, info)
	local formatter = compiledTemplateCache[tostring(template)]
	if type(formatter) == "function" then
		wipe(variables)
		for name, value in pairs(info) do
			if value then
				value = strtrim(tostring(value))
				if value ~= "" then
					variables[strupper(name)] = value
				end
			end
		end
		return formatter(variables)
	end
end

function CastYeller2:ValidateTemplate(template)
	local ok, func = Compile(template)
	if ok then
		return true
	else
		return func
	end
end

--[===[@debug@
if testing then
	--[==[
	local fmt = '$SPELL[ on ${TARGET}[ ${RAIDICONTEXTURE}]] failed[: ${REASON}]'
	local values = {
		spell = 'Lifebloom',
		target = 'Qwetia',
		raidicontexture = '{rt5}',
		reason = 'you fail !'
	}
	local info = {}
	for k, v in pairs(values) do
		print(k, '=', v)
		info[k] = v
		print(' ', CastYeller2:FormatMessage(fmt, info))
	end
	--]==]
	print(Compile('You fail at $SPELL %[.'))
	
end
--@end-debug@]===]

